package com.authsphere.security.account.common.bind;

import com.authsphere.common.result.ResponseResult;
import com.authsphere.common.result.ResponseResultUtils;
import com.authsphere.plugin.api.WebExchange;
import com.authsphere.security.account.api.bind.AccountBindTempTokenRepository;
import com.authsphere.security.account.api.bind.AccountNotBindContext;
import com.authsphere.security.account.api.bind.BindingExecutor;
import com.authsphere.security.account.api.bind.InMemoryAccountBindTempTokenRepository;
import com.authsphere.security.account.api.bind.TempBindToken;
import com.authsphere.security.account.api.bind.user.ExternalUser;
import com.authsphere.security.account.common.config.SecurityAccountConfig;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.IOException;
import java.net.URI;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Map;
import java.util.Objects;

/**
 * @program: AuthSphere
 * @description:
 * @author: YuKai Fan
 * @create: 2025/3/20 22:10
 **/
public class PersistentTokenBasedAccountBindService extends AbstractAccountBindService implements InitializingBean {
    private final AccountBindTempTokenRepository tokenRepository;

    private final BindingExecutor bindingExecutor;

    private final SecureRandom random;

    public PersistentTokenBasedAccountBindService(
            BindingExecutor bindingExecutor,
            SecurityAccountConfig.BindAccountConfig bindAccountConfig,
            AccountBindTempTokenRepository tokenRepository) {
        super(bindAccountConfig);
        this.random = new SecureRandom();
        this.tokenRepository = tokenRepository;
        this.bindingExecutor = bindingExecutor;
    }

    @Override
    protected void onNoBind(HttpServletRequest request, HttpServletResponse response, AccountNotBindContext context) throws IOException {
        ExternalUser externalUser = context.getExternalUser();
        String tokenValue = bindAccountConfig.getTokenPrefix() + generateTokenData();
        TempBindToken token = new TempBindToken(tokenValue, externalUser, bindAccountConfig.getTokenExpireTime());
        this.tokenRepository.createNewToken(token);
        Map<String, Object> data = Map.of("token", tokenValue, "timestamp", System.currentTimeMillis(), "hasBind", false);
        if (context.isAppClient() || StringUtils.isBlank(context.getRedirectUrl())) {
            // 直接返回token
            ResponseResultUtils.result(response, ResponseResult.ofSuccess(304, "账号未绑定", data));
        } else {
            // 跳转到url
            URI uri = UriComponentsBuilder.fromHttpUrl(context.getRedirectUrl())
                    .build(data);
            getRedirectStrategy().sendRedirect(request, response, uri.toString());
        }
    }

    @Override
    protected void onBind(WebExchange exchange) {
        HttpServletRequest request = exchange.getRequest();
        HttpServletResponse response = exchange.getResponse();
        String token = request.getParameter(bindAccountConfig.getTokenParameter());
        TempBindToken tempBindToken = tokenRepository.getTokenForValue(token);
        if (Objects.isNull(tempBindToken)) {
            if (!response.isCommitted()) {
                ResponseResultUtils.result(response, ResponseResult.ofFailed0(502, "token已过期"));
                exchange.setAbort(true);
            }
            return;
        }
        ExternalUser externalUser = tempBindToken.getExternalUser();
        // 当前用户认证信息
        Object principal = exchange.getAuthentication().getPrincipal();
        boolean result = bindingExecutor.execute(externalUser, principal);
        if (!result && !response.isCommitted()) {
            ResponseResultUtils.result(response, ResponseResult.ofFailed0(500, "账号绑定失败，请重新授权"));
            exchange.setAbort(true);
        } else {
            this.logger.trace(String.format("账号绑定执行结果：%s", result));
        }
    }

    protected String generateTokenData() {
        byte[] newToken = new byte[bindAccountConfig.getTokenLength()];
        this.random.nextBytes(newToken);
        return new String(Base64.getEncoder().encode(newToken));
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(bindingExecutor, "BindingExecutor can not be null");
        Assert.notNull(tokenRepository, "AccountBindTempTokenRepository can not be null");
    }
}
